package routes {
	
	import flash.events.Event;
	
	import mx.utils.ObjectUtil;
	
	/**
	 * The routes.ApplicationEvent class defines application events for 
	 * applications adopting a loosely-coupled internal architecture.
	 * 
	 * <p><b>Single class is sufficient</b>: 
	 * Using a single event class reduces code bloat as it avoids having to 
	 * create numerous similar event classes, and is in itself a Flex best 
	 * practice. Still, it is a matter of choice whether a specific 
	 * application should define more dedicated subclasses of this generic 
	 * event class.</p>
	 * 
	 * <p><b>Adaptive behaviour</b>: 
	 * By default, the ApplicationEvent class remembers all event types 
	 * it is created with. This behaviour may be changed by setting 
	 * <code>autoSetTypes</code> to false, after which all types must be 
	 * declared either with <code>setType()</code> or with 
	 * <code>setTypes()</code>. A count of how many instances 
	 * are created for each type is also maintained (call 
	 * <code>listTypes()</code> for a status snapshot).</p>
	 * 
	 * <p><b>Dynamic behaviour</b>:
	 * To facilitate passing data around, any data may be attached to an 
	 * ApplicationEvent instance. This functionality is provided via the 
	 * <code>getItem(key)</code> and <code>setItem(key, value)</code> methods.
	 * Note that while the behaviour is dynamic, the ApplicationEvent class 
	 * itself is not <code>dynamic</code> but the private <code>_data</code> 
	 * attribute is -- however this is initialized only if custom data is set 
	 * on the event instance, thus liberal usage of this event class does not 
	 * incur any performance or memory overhead. 
	 * See the <code>{set/get/has/delete}Item()</code> methods.</p> 
	 * 
	 * <p><b>Chains of Events</b>:
	 * The <code>yield()</code> method returns a new ApplicationEvent (of a 
	 * different type) with the current ApplicationEvent instance as its 
	 * <code>_previous</code> event. This makes it possible to create chains 
	 * of related events, where each event in the chain just automagically 
	 * <em>inherits</em> any data item set on any preceeding event in the 
	 * chain, as if they were set on itself.</p> 
	 */
	public class ApplicationEvent extends Event {
		
		// Types 
		
		/** 
		 * Application-wide eventType bookkeeping.
		 * 
		 * <p>A class-level dynamic object where each property name defines a 
		 * type, while the value is a running count of events created for 
		 * the type:
		 * <code>{ type:String : count:int }</code></p>
		 * */
		public static var types:Object = {};
		
		/** 
		 * Allow automatic type accumulation.
		 * 
		 * <p>When true (the default), the class will automatically accumulate 
		 * all the type strings that instances are created with. Changing 
		 * <code>autoSetTypes</code> to false will disallow this auto-adaptive
		 * learning of event types -- in this case the precise list of types
		 * supported by the application <b>must</b> be explicitly specified,
		 * by calling <code>ApplicationEvent.setTypes(myTypesArray)</code> 
		 * and/or by calling <code>ApplicationEvent.setType(type)</code> 
		 * for each type.</p> 
		 * */
		public static var autoSetTypes:Boolean = true;  
		
		/** 
		 * Set type to list of known types.
 		 */
		public static function setType(type:String):void {
			types[type] = 0;
		}
		
		/** 
		 * Set each type in types to list of known types.
 		 */
		public static function setTypes(types:Array):void {
			var type:String;
			for each (type in types) {
				setType(type);
			}
		}
		
		/**
		 * Is this type known?
		 */
		public static function hasType(type:String):Boolean {
			return types.hasOwnProperty(type);
		}
		
		/**
		 * Return a string listing of known event types. 
		 */
		public static function listTypes():String {
			return "ApplicationEvent.types:" + ObjectUtil.toString(types);
		}
		
		protected function _countType(type:String):void {
			types[type]++;
		}
		
		// Data
		
		/** 
		 * the previous ApplicationEvent, if any, from which this one was 
		 * yielded -- set in yield().
		 * */
		protected var _previous:ApplicationEvent;
		
		/** 
		 * the dynamic container object for any custom event data --
		 * initialized in setItem() only if needed. 
		 * */
		protected var _data:Object;
		
		/** 
		 * Lookup value for this key. If (previous), then also lookup backwards 
		 * in any linked previous events. Return first found. 
		 *  
		 * @return null if nothing had been previously stored for "key" 
		 * */
		public function getItem(key:String, previous:Boolean=true):Object {
			if (hasItem(key, false)) {
				return _data[key];
			}
			else if (previous && _previous!=null) {
				return _previous.getItem(key, true);
			}
			return null;
		}
		
		/** 
		 * Sets data value, under key, on this instance.
		 * */  
		public function setItem(key:String, value:Object):void {
			if (_data==null) {
				_data = {};
			}
			_data[key] = value; 
		}
		
		/** 
		 * Check if key is defined as data item. If (previous), then also check 
		 * backwards in any linked previous events.
		 * */  
		public function hasItem(key:String, previous:Boolean=true):Boolean {
			if (_data!=null) {
				if (_data.hasOwnProperty(key)) {
					return true;
				}
				else if (previous && _previous!=null) {
					return _previous.hasItem(key, true);
				}
			}
			return false;
		}
		
		/** 
		 * Delete the data item for key defined on this instance. 
		 * If (previous), then also delete any data item defined for key
		 * on all linked previous events.
		 * */  
		public function deleteItem(key:String, previous:Boolean=true):void {
			if (hasItem(key, false)) {
				delete _data[key];
			}
			if (previous && _previous!=null) {
				deleteItem(key, true);
			}
		}
		
		// Constructor 
		
		/**
		 * @param type		same as super
		 * @param bubbles	same as super, but defaults to true   
		 * @param type		same as super, but defaults to false
		 * @param data		dynamic data object for the event instance
		 * */
		public function ApplicationEvent(type:String, 
				bubbles:Boolean=true, 
				cancelable:Boolean=false,
				data:Object=null) {
			super(type, bubbles, cancelable);
			_data = data;
			// auto-adaptive learning of types
			if (!hasType(type)) {
				if (autoSetTypes) {
					setType(type);
				}
				else {
					throw new ArgumentError(
						"No such ApplicationEvent type: "+type);
				}
			}
			// counter, by type
			_countType(type);
		}
		
		// Generic Event methods 
		
		// May need to adjust some base Event methods e.g. clone().
		// 
		// For the list of base Event class methods see:
		// http://livedocs.adobe.com/flex/3/langref/flash/events/Event.html
		//
		// However, it is anyway not possible in ActionScript to "refine" the 
		// returnType of an overriden method, see:
 		// http://kuwamoto.org/2007/01/22/covariant-property-types/
		
		/** 
		 * override clone e.g for redispatching
		 * any custom data is copied over
		 * */
		override public function clone():Event {
			return new ApplicationEvent(type, bubbles, cancelable, 
				ObjectUtil.copy(_data));
		}
		
		/** 
		 * Yields another ApplicationEvent and <em>chains</em> self as 
		 * its <code>_previous</code> ApplicationEvent instance. 
		 * 
		 * <p>A chain of events may not repeat types i.e. for any given event 
		 * type a chain may only contain one event of that type.</p>
		 * 
		 * <p>A yielded ApplicationEvent automatically <em>inherits</em> all 
		 * data of previous events while still being free to override or add 
		 * any data value and with no modifications to any chained 
		 * predecessor.</p>
		 * 
		 * @see ApplicationEvent#getItem()
		 * */
		public function yield(type:String, data:Object=null):ApplicationEvent {
			if (type==this.type || isPredecessor(type)) {
				throw new ArgumentError(
					"A yielded applicationEvent must have a different type: " +
					type);
			}
			var ae:ApplicationEvent = 
					new ApplicationEvent(type, bubbles, cancelable, data);
			ae._previous = this;
			return ae;
		}
		
		/**
		 * Is there a predecessor event of this type?
		 */
		protected function isPredecessor(type:String):Boolean {
			if (_previous!=null) {
				if (_previous.type==type) {
					return true;
				}
				else {
					return _previous.isPredecessor(type);
				}
			}
 			return false;
		}
		
	
	}
}
